using System;
using System.Collections.Generic;
using System.Text;
using System.Drawing;
using System.Windows.Forms;
using System.IO;
using SlimDX;
using SlimDX.Direct3D9;

using System.Threading;


namespace Splino
{
    namespace D3D
    {
        /// <summary>
        /// Enumerator used for switch statements depending on
        /// which camera type is used
        /// </summary>
        public enum CameraType 
        {
            /// <summary>
            /// An orthographic camera
            /// </summary>
            Orthographic,
            /// <summary>
            /// A perspective camera
            /// </summary>
            Perspective 
        }

        /// <summary>
        /// Manages the Direct3D device, handles render states and
        /// presentation parameters, and renders the scene to the
        /// viewport swapchains
        /// </summary>
        public class DeviceManager
        {
            Device _device = null; 
            /// <summary>
            /// Gets the Direct3D hardware device
            /// </summary>
            public Device Device
            {
                get { return _device; }
            }

            SplinoViewport _viewportTop = null;
            /// <summary>
            /// Gets the top SplinoViewport
            /// </summary>
            public SplinoViewport ViewportTop
            {
                get { return _viewportTop; }
                set { _viewportTop = value; }
            }

            SplinoViewport _viewportFront = null;
            /// <summary>
            /// Gets the front SplinoViewport
            /// </summary>
            public SplinoViewport ViewportFront
            {
                get { return _viewportFront; }
                set { _viewportFront = value; }
            }

            SplinoViewport _viewportRight = null;
            /// <summary>
            /// Gets the right SplinoViewport
            /// </summary>
            public SplinoViewport ViewportRight
            {
                get { return _viewportRight; }
                set { _viewportRight = value; }
            }

            SplinoViewport _viewportPerspective = null;
            /// <summary>
            /// Gets the perspective SplinoViewport
            /// </summary>
            public SplinoViewport ViewportPerspective
            {
                get { return _viewportPerspective; }
                set { _viewportPerspective = value; }
            }

            List<SplinoViewport> _viewports = new List<SplinoViewport>();
            /// <summary>
            /// Gets a collection of every Splino viewport.
            /// </summary>
            public List<SplinoViewport> Viewports { get { return _viewports; }}

            /// <summary>
            /// Device Presentation Parameters
            /// </summary>
            PresentParameters _presentParams = new PresentParameters();
            /// <summary>
            /// Gets the presentation parameters for the D3D device.
            /// </summary>
            public PresentParameters PresentParameters { get { return _presentParams; } }

            /// <summary>
            /// True to pause all rendering
            /// </summary>
            bool _devicePaused = false;

            bool _deviceLost = false;

            /// <summary>
            /// Gets if the Direct3D device rendering is paused
            /// </summary>
            public bool DevicePaused { get { return _devicePaused; } }

            /// <summary>
            /// True if the scene has changed and needs to be rendered.  Only used in
            /// As Needed rendering mode.
            /// </summary>
            bool _renderRequested = true;
            /// <summary>
            /// Gets whether a render is pending in the device manager, only used
            /// in As Needed rendering mode.
            /// </summary>
            public bool RenderRequested
            {
                get { return _renderRequested; }
                set { _renderRequested = value; }
            }

            /// <summary>
            /// For FPS calculation
            /// </summary>
            int _prevTickCount = Environment.TickCount;
            double _updateCount = 0;
            int _fpsComputations;

            SlimDX.Direct3D9.Font _axesGuideFont;
            /// <summary>
            /// Gets the font used to label teh axes indicator guide
            /// </summary>
            public SlimDX.Direct3D9.Font AxesGuideFont
            {
                get { return _axesGuideFont; }
            }

            CameraType _selectionRectangleViewPort = new CameraType();
            /// <summary>
            /// Gets or sets the ViewPort that the selection rectangle will be drawn in
            /// </summary>
            public CameraType SelectionRectangleViewPort
            {
                get { return _selectionRectangleViewPort; }
                set { _selectionRectangleViewPort = value; }
            }

            bool _drawSelectionRectangle = false;
            /// <summary>
            /// Gets or sets if the selection rectangle is to be drawn
            /// </summary>
            public bool DrawSelectionRectangle
            {
                get { return _drawSelectionRectangle; }
                set { _drawSelectionRectangle = value; }
            }

            /// <summary>
            /// Gets whether the DeviceManager.Instance object has been
            /// created.  Useful for preventing infinite
            /// loops during singleton accesses of uninitialized objects.
            /// </summary>
            public static bool Initialized
            {
                get
                {
                    if (_instance != null)
                        return true;
                    return false;
                }
            }

            private static readonly object padlock = new object();
            private static volatile DeviceManager _instance;
            /// <summary>
            /// The singleton instance for the Device Manager
            /// </summary>
            public static DeviceManager Instance
            {
                get
                {
                    if (_instance == null)
                        lock (padlock)
                        {
                            if (_instance == null)
                                _instance = new DeviceManager();
                        }
                    return _instance;
                }
            }
            private DeviceManager()
            {
                InitializeDevice();
            }

            /// <summary>
            /// Creates the four default viewports
            /// </summary>
            public void SetupDefaultViewports()
            {                
                _viewportTop = new SplinoViewport(CameraType.Orthographic, new Vector3(1,0,0), new Vector3(0,0,1));
                _viewportFront = new SplinoViewport(CameraType.Orthographic, new Vector3(1, 0, 0), new Vector3(0, 1, 0));
                _viewportRight = new SplinoViewport(CameraType.Orthographic, new Vector3(0, 0, 1), new Vector3(0, 1, 0));
                _viewportPerspective = new SplinoViewport(CameraType.Perspective, new Vector3(1, 0, 0), new Vector3(0, 0, 1));

                _viewports = new List<SplinoViewport>();

                _viewports.Add(_viewportTop);
                _viewports.Add(_viewportFront);
                _viewports.Add(_viewportRight);
                _viewports.Add(_viewportPerspective);

                SplinoForm.Instance.SplinoWindows[0].SplinoViewport = _viewportRight;
                SplinoForm.Instance.SplinoWindows[0].Text = "Right";
                SplinoForm.Instance.SplinoWindows[1].SplinoViewport = _viewportPerspective;
                SplinoForm.Instance.SplinoWindows[1].Text = "Perspective";
                SplinoForm.Instance.SplinoWindows[2].SplinoViewport = _viewportFront;
                SplinoForm.Instance.SplinoWindows[2].Text = "Front";
                SplinoForm.Instance.SplinoWindows[3].SplinoViewport = _viewportTop;
                SplinoForm.Instance.SplinoWindows[3].Text = "Top";

                _viewportTop.CameraPosition = new Vector3(0.0f, 100.0f, 0.0f);
                _viewportTop.CameraTarget = new Vector3(0.0f, 0.0f, 0.0f);
                _viewportTop.CameraUpVector = new Vector3(0.0f, 0.0f, 1.0f);

                _viewportTop.Width = 5f;
                _viewportTop.ZNearPlane = 0.005f;
                _viewportTop.ZFarPlane = 500.0f;

                _viewportTop.UpdateMatrices();

                _viewportPerspective.CameraPosition = new Vector3(0.0f, 2.5f, -1.5f);
                _viewportPerspective.CameraTarget = new Vector3(0f, 0f, 0f);
                _viewportPerspective.CameraUpVector = Vector3.Cross(
                    _viewportPerspective.CameraDirection,
                    new Vector3(1f, 0f, 0f));


                _viewportPerspective.FieldOfViewY = (float)(Math.PI / 4);
                _viewportPerspective.ZNearPlane = 0.005f;
                _viewportPerspective.ZFarPlane = 500.0f;

                _viewportPerspective.UpdateMatrices();

                _viewportRight.CameraPosition = new Vector3(100f, 0f, 0f);
                _viewportRight.CameraTarget = new Vector3(0f, 0f, 0f);
                _viewportRight.CameraUpVector = new Vector3(0f, 1f, 0f);

                _viewportRight.Width = 5f;
                _viewportRight.ZNearPlane = 0.005f;
                _viewportRight.ZFarPlane = 500.0f;

                _viewportRight.UpdateMatrices();

                ViewportFront.CameraPosition = new Vector3(0f, 0f, -100f);
                ViewportFront.CameraTarget = new Vector3(0f, 0f, 0f);
                ViewportFront.CameraUpVector = new Vector3(0f, 1f, 0f);

                _viewportFront.Width = 5f;
                _viewportFront.ZNearPlane = 0.005f;
                _viewportFront.ZFarPlane = 500.0f;

                ViewportFront.UpdateMatrices();
            }

            /// <summary>
            /// Initializes the Direct3D device setting the presentation
            /// parameters and render states
            /// </summary>
            /// <returns>True if the device was successfully created</returns>
            public bool InitializeDevice()
            {
                // Get the current desktop display mode, so we can set up a back
                // buffer of the same format

                // Set up the structure used to create the D3DDevice. Since we are now
                // using more complex geometry, we will create a device with a zbuffer.
                _presentParams.Windowed = true;
                _presentParams.SwapEffect = SwapEffect.Discard;
                _presentParams.EnableAutoDepthStencil = true;
                _presentParams.AutoDepthStencilFormat = Format.D16;
                _presentParams.BackBufferCount = 1;
                _presentParams.BackBufferFormat = Format.Unknown;
                //_presentParams.MultiSample = MultiSampleType.NonMaskable;
                //_presentParams.MultiSampleQuality = 1;
                _presentParams.PresentationInterval = PresentInterval.One;
                _presentParams.BackBufferWidth = SplinoWindow.LargestSplinoWindowSize.Width;
                _presentParams.BackBufferHeight = SplinoWindow.LargestSplinoWindowSize.Height;
                

                // Create the D3DDevice
                _device = new Device(
                    new Direct3D(),
                    0,
                    DeviceType.Hardware,
                    SplinoForm.Instance.Handle,
                    CreateFlags.SoftwareVertexProcessing,
                    _presentParams);

                

                _axesGuideFont = new SlimDX.Direct3D9.Font(
                    _device,
                    14,
                    6,
                    FontWeight.Normal,
                    0,
                    false,
                    CharacterSet.Default,
                    Precision.Default,
                    FontQuality.ClearType,
                    PitchAndFamily.DontCare,
                    "Microsoft San Serif");

                _device.SetRenderState(RenderState.FillMode, FillMode.Solid);
                _device.SetRenderState(RenderState.CullMode, Cull.None);
                _device.SetRenderState(RenderState.AntialiasedLineEnable, true);
                //_device.SetRenderState(RenderState.ZEnable, ZBufferType.UseWBuffer);
                _device.SetRenderState(RenderState.Ambient, Color.White.ToArgb());
                _device.SetRenderState(RenderState.Lighting, false);
                _device.SetRenderState(RenderState.FillMode, FillMode.Solid);
                _device.SetRenderState(RenderState.AntialiasedLineEnable, true);
                _device.SetRenderState(RenderState.PointSpriteEnable, true);
                _device.SetRenderState(RenderState.PointSize, 3.0f);

                _devicePaused = false;

                return true;
            }

            /// <summary>
            /// Event handler for resetting the Direct3D device after
            /// recovering from a lost device
            /// </summary>
            /// <param name="sender">Event initiator</param>
            /// <param name="e">Event arguments</param>
            public void OnResetDevice(object sender, EventArgs e)
            {
                _deviceLost = false;
                _device.SetRenderState(RenderState.FillMode, FillMode.Solid);
                _device.SetRenderState(RenderState.CullMode, Cull.None);
                _device.SetRenderState(RenderState.AntialiasedLineEnable, true);
                //_device.SetRenderState(RenderState.ZEnable, ZBufferType.UseWBuffer);
                _device.SetRenderState(RenderState.Ambient, Color.White.ToArgb());
                _device.SetRenderState(RenderState.Lighting, false);
                _device.SetRenderState(RenderState.FillMode, FillMode.Solid);
                _device.SetRenderState(RenderState.AntialiasedLineEnable, true);
                _device.SetRenderState(RenderState.PointSize, 3.0f);

                SceneManager.Instance.ResetScene();

                foreach (SplinoViewport splinoViewport in Viewports)
                {
                    splinoViewport.ResetGrid();
                    splinoViewport.ResetAxesGuide();
                }
            }

            /// <summary>
            /// Resets the Direct3D device
            /// </summary>
            public void Reset()
            {
                //_presentParams.BackBufferWidth = SplinoWindow.LargestSplinoWindowSize.Width;
                //_presentParams.BackBufferHeight = SplinoWindow.LargestSplinoWindowSize.Height;
                //_device.Reset(_presentParams);
                OnResetDevice(null, null);
            }

            /// <summary>
            /// Checks to see if the Direct3D device can be recovered, and if it can,
            /// disposes it resources and recreates them.
            /// </summary>
            public void AttemptDeviceReset()
            {
                Result result = _device.TestCooperativeLevel();
                
                if (result == ResultCode.Success)
                    ApplicationHelper.Instance.UnhookIdleResetEvent();
                if (result == ResultCode.DeviceLost)
                    SceneManager.Instance.DisposeScene();
                if (result == ResultCode.DeviceNotReset)
                {
                    SceneManager.Instance.DisposeScene();
                    _presentParams.BackBufferWidth = SplinoWindow.LargestSplinoWindowSize.Width;
                    _presentParams.BackBufferHeight = SplinoWindow.LargestSplinoWindowSize.Height;
                    _device.DepthStencilSurface = Surface.CreateDepthStencil(
                        _device,
                        _presentParams.BackBufferWidth,
                        _presentParams.BackBufferHeight,
                        Format.D16,
                        MultisampleType.None,
                        0,
                        true);
                       _device.Reset(_presentParams);
                    Reset();
                }
            }

            /// <summary>
            /// Pauses all rendering
            /// </summary>
            public void PauseRenderer()
            {
                _devicePaused = true;
                ApplicationHelper.Instance.UnhookIdleDrawEvent();
            }

            /// <summary>
            /// Unpauses all rendering
            /// </summary>
            public void UnpauseRenderer()
            {
                _devicePaused = false;
                ApplicationHelper.Instance.HookIdleDrawEvent();
            }

            /// <summary>
            /// Renders the scene to all unpaused viewports
            /// </summary>
            public void Render()
            {
                if (_devicePaused)
                    return;

                if (_deviceLost)
                {
                    Reset();
                    return;
                }

                foreach (SplinoViewport splinoViewport in Viewports)
                {
                    if (!splinoViewport.Paused)
                    {
                        try { 
                            _device.SetRenderTarget(0, splinoViewport.BackBuffer);
                            _device.DepthStencilSurface = splinoViewport.DepthStencilBuffer;
                        }
                        catch
                        {
                            foreach (SplinoViewport viewport in _viewports)
                                splinoViewport.ResizeSwapChain();
                            return;
                        }

                        _device.SetTransform(TransformState.View, splinoViewport.MatrixView);
                        _device.SetTransform(TransformState.Projection, splinoViewport.MatrixProjection);
                        RenderScene(splinoViewport);
                        try { splinoViewport.SwapChain.Present(
                            Present.None, 
                            splinoViewport.ViewportWindow.ClientRectangle,
                            splinoViewport.ViewportWindow.ClientRectangle,
                            splinoViewport.ViewportWindow.PresentationPanel.Handle); }
                        catch
                        {
                            PauseRenderer();
                            ApplicationHelper.Instance.HookIdleResetEvent();
                            return;
                        }
                    }
                }

                if (SplinoForm.Instance.ContinuousRenderingMode)
                    ComputeFPS();
                RENDERCOUNT++;
                _renderRequested = false;
            }

            private int RENDERCOUNT = 0;

            /// <summary>
            /// Renders the scene geometry
            /// </summary>
            /// <param name="splinoViewport">The viewport being rendered to</param>
            private void RenderScene(SplinoViewport splinoViewport)
            {
                _device.Clear(
                    ClearFlags.Target | ClearFlags.ZBuffer,
                    Splino.Palette.splino00,
                    1f,
                    0);

                _device.BeginScene();

                _device.SetTransform(TransformState.World, Matrix.Identity);
                _device.Viewport = splinoViewport.ViewportBottomMost;
                splinoViewport.DrawMinorGrid();
                splinoViewport.DrawMajorGrid();
                splinoViewport.DrawAxes();
                SceneManager.Instance.DrawBottomMostSceneObjects(splinoViewport);

                _device.Viewport = splinoViewport.ViewportTopMost;
                SceneManager.Instance.DrawTopMostSceneObjects(splinoViewport);
                splinoViewport.DrawAxesGuide();
                _device.Viewport = splinoViewport.Viewport;

                if (_drawSelectionRectangle && splinoViewport.ViewportWindow.Focused)
                    SceneManager.Instance.DrawSelectionRectangle();

                SceneManager.Instance.DrawSceneLayerSceneObjects(splinoViewport);
                _device.EndScene();
            }

            /// <summary>
            /// Computes the frames per second being rendered for the
            /// individual viewports
            /// </summary>
            private void ComputeFPS()
            {
                double currentCount = Environment.TickCount - _prevTickCount;
                _prevTickCount = Environment.TickCount;
                _updateCount += currentCount;
                _fpsComputations++;

                if (_updateCount > 1000)
                {
                    SplinoForm.Instance.FPS = _fpsComputations;
                    _updateCount = 0;
                    _fpsComputations = 0;
                    SplinoForm.Instance.UpdateFPS();
                }
            }

            /// <summary>
            /// Clears Scene Manager and reinitializes the Device Manager
            /// </summary>
            public void ClearDeviceManager()
            {
                foreach (SplinoObject splinoObject in 
                    SceneManager.Instance.MasterSceneObject.SceneChildren)
                    if (splinoObject.PropertiesWindowActive)
                        splinoObject.ClosePropertiesWindow();
                if (SceneManager.Instance.MasterSceneObject.PropertiesWindowActive)
                    SceneManager.Instance.MasterSceneObject.ClosePropertiesWindow();
                _instance = null;
            }

            /// <summary>
            /// Asks the device manager to render the scene, only
            /// </summary>
            public void RequestRender()
            {
                _renderRequested = true;
            }
        }
    }
}
